#!/usr/bin/env bash
#
# Copyright 2016 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Set PROGram name
PROG=${0##*/}
########################################################################
#+
#+ NAME
#+     $PROG - Fast forward a branch to master via merge
#+
#+ SYNOPSIS
#+     $PROG  [--nomock] <branch> [master object]
#+     $PROG  [--helpshort|--usage|-?]
#+     $PROG  [--help|-man]
#+
#+ DESCRIPTION
#+     $PROG fast forwards a K8s release branch to [master object] (defaults to
#+     HEAD and then prepares the branch as a K8s release branch:
#+     * Run hack/update-all.sh to ensure compliance of generated files
#+
#+
#+ OPTIONS
#+     [--nomock]                 - Actually push this upstream
#+     [--help | -man]            - display man page for this script
#+     [--usage | -?]             - display in-line usage
#+
#+ EXAMPLES
#+     $PROG release-1.3          - Resync master@HEAD onto release-1.3 branch
#+     $PROG release-1.3 39d0135e - Resync master@39d0135e onto release-1.3
#+                                  branch
#+
#+ FILES
#+
#+ SEE ALSO
#+     lib/common.sh              - base function definitions
#+     lib/gitlib.sh              - git function definitions
#+
#+ BUGS/TODO
#+
########################################################################
# If NO ARGUMENTS should return *usage*, uncomment the following line:
usage=${1:-yes}

source $(dirname $(readlink -ne $BASH_SOURCE))/lib/common.sh

# Check prerequisites

PREREQUISITE_TOOLS=("git" "jq" "go" "make")
for PREREQ in ${PREREQUISITE_TOOLS[@]}; do
  command -v $PREREQ > /dev/null 2>&1 || common::exit 1 "$PREREQ not installed"
done;

source $TOOL_LIB_PATH/gitlib.sh

# Set positional args
RELEASE_BRANCH=${POSITIONAL_ARGV[0]}
MASTER_OBJECT=${POSITIONAL_ARGV[1]:-"origin/master"}

# Check branch format
[[ $RELEASE_BRANCH =~ $BRANCH_REGEX ]] || common::exit 1 "Invalid branch name!"

# Branch Exists
gitlib::branch_exists $RELEASE_BRANCH \
 || common::exit 1 "$RELEASE_BRANCH doesn't exist.  Exiting..."

##############################################################################
# Initialize logs
##############################################################################
# Initialize and save up to 10 (rotated logs)
MYLOG=$TMPDIR/$PROG.log
common::logfileinit $MYLOG 10
# BEGIN script
common::timestamp begin

gitlib::repo_state || common::exit 1

###############################################################################
# MAIN
###############################################################################
BASEDIR="$(mktemp -d "$TMPDIR/$PROG.XXXXXX")"
# WORK/BUILD area
WORKDIR=$BASEDIR/$PROG-$RELEASE_BRANCH
# Go tools expect the kubernetes src to be under $GOPATH
export GOPATH=$WORKDIR
# TREE_ROOT is working branch/tree
TREE_ROOT=$WORKDIR/src/k8s.io/kubernetes

# Set a trap to cleanup BASEDIR if --cleanup is set.
if (($FLAGS_cleanup)) ; then
  trap 'rm -rf "$BASEDIR"' EXIT ERR SIGINT SIGQUIT SIGTERM SIGHUP
fi

# The real deal?
if ((FLAGS_nomock)); then
  DRYRUN_FLAG=""
else
  DRYRUN_FLAG=" --dry-run"
fi

logecho
logecho "Follow along in detail: tailf $MYLOG"

##############################################################################
common::stepheader "SESSION DETAILS"
##############################################################################
logecho "$ATTENTION: This is a${DRYRUN_FLAG:-"n ACTUAL"} run!"

##############################################################################
common::stepheader "SYNC A WORKING REPO"
##############################################################################
if [[ -d $WORKDIR ]]; then
  common::askyorn "$WORKDIR exists.  Delete" || common::exit 1 "Exiting..."
  logrun rm -rf $WORKDIR || common::exit 1 "Exiting..."
fi

logrun mkdir -p $WORKDIR
gitlib::sync_repo $K8S_GITHUB_AUTH_URL $TREE_ROOT || common::exit 1 "Exiting..."
logrun cd $TREE_ROOT

# Is the master branch in a reasonable state to fast forward over the branch?
# When git merges from master to branch, the list of tags is overlaid and
# sorted chronologically.
# MERGE_BASE: Show common ancestor object/tag
# MASTER_DESCRIBE: Show the latest tag on origin/master
# ANCESTOR_DESCRIBE: Show the tag associated with MERGE_BASE
MERGE_BASE=$(git merge-base origin/master origin/$RELEASE_BRANCH)
ANCESTOR_DESCRIBE=$(git describe --abbrev=0 --tags $MERGE_BASE)
MASTER_DESCRIBE=$(git describe --abbrev=0 --tags origin/master)
# If these are different, there's a newer (than the branch) tag on master
# and we cannot do a FF
if [[ ! $MASTER_DESCRIBE == $ANCESTOR_DESCRIBE ]]; then
  logecho
  logecho "$FATAL!  master branch has been tagged to a newer version" \
          "($MASTER_DESCRIBE)." \
          "Unable to fast-forward $RELEASE_BRANCH branch to master."
  common::exit 1
fi

##############################################################################
common::stepheader "CHECK GIT PUSH ACCESS"
##############################################################################
# TODO: capture state of access without forcing us into a prompt I have to
#       expose.
logecho -n "Checking git push access (verbosely to accept password if needed)"
logrun -v git push -q --dry-run $K8S_GITHUB_AUTH_URL || common::exit 1 "Exiting..."

##############################################################################
common::stepheader "CHECK OUT BRANCH"
##############################################################################
logecho -n "Checking out $RELEASE_BRANCH: "
logrun -s git checkout -b $RELEASE_BRANCH origin/$RELEASE_BRANCH \
 || common::exit 1 "Exiting..."

##############################################################################
common::stepheader "MERGE AND UPDATE"
##############################################################################
logecho -n "Merging $MASTER_OBJECT into $RELEASE_BRANCH branch: "
# If merge results in conflicts with CHANGELOG* files due to previous version
# releases occurring between branch and branchff, resolve specifically in favor
# of the branched (deleted) version of those files.
if ! logrun -s git merge -X ours $MASTER_OBJECT; then
  DELETED_FILES=($(git status -s |\
                   awk '$1 == "DU" && $2 ~ /CHANGELOG-/ {print $2}'))

  ((${#DELETED_FILES[*]}==0)) \
   && common::exit "Unable to resolve conflict automatically.  Check $MYLOG"

  logecho "Merge conflict with files deleted on branch (${DELETED_FILES[*]})." \
          "Accepting deletes and continuing..."
  for f in ${DELETED_FILES[*]};do
    logrun git rm $f
  done
  logecho -n "Merging $MASTER_OBJECT into $RELEASE_BRANCH branch (try 2): "
  logrun -s git commit -m "Merge remote-tracking branch 'origin/master' into $RELEASE_BRANCH.  Deleting ${DELETED_FILES[*]}" || common::exit 1 "Exiting..."
fi

# TODO: This should use update-all again when
# - run-time returns to reasonable levels
# - it doesn't run update-generated-docs.sh which creates placeholder docs
#   for *master* only
# - it doesn't result in failures like this:
# <snip>
# Running update-staging-godeps
#
#Unexpected dirty working directory:
#
# M api/openapi-spec/swagger.json
# M docs/admin/cloud-controller-manager.md
# M docs/admin/kube-apiserver.md
# M docs/admin/kube-controller-manager.md
# M docs/admin/kube-proxy.md
# <snip>
# Commit your changes in another terminal and then continue here by pressing enter.

USEFUL_UPDATES=(update-openapi-spec)

logecho -n "Ensure etcd is up to date: "
logrun -s hack/install-etcd.sh
export PATH="$TREE_ROOT/third_party/etcd:$PATH"

logecho "Run 'useful' (the list may be out of date) hack/update* scripts..."
for script in ${USEFUL_UPDATES[*]}; do
  fullscript="hack/$script.sh"
  if ! [[ -f $fullscript ]]; then
    logecho "Skipping non-existent $script..."
    continue
  fi
  logecho -n "Running $fullscript: "
  logrun -s $fullscript
done

if [[ -n "$(git status -s)" ]]; then
  logecho -n "Commit changes: "
  logrun git add -A
  logrun -s git commit -am \
                "Results of running update scripts: ${USEFUL_UPDATES[*]}" \
    || common::exit 1 "Exiting..."
fi

##############################################################################
common::stepheader "PUSH UPSTREAM"
##############################################################################
rev_origin_release="$( git rev-parse --short "origin/${RELEASE_BRANCH}" )"
rev_head="$( git rev-parse --short "HEAD" )"

logecho "Go look around in $TREE_ROOT to make sure things look ok:"
logecho "# Any files left uncommitted?"
logecho "* git status -s"
logecho "# What does the commit look like?"
logecho "* git show"
logecho "# What are the changes we just pulled in from master?"
logecho "#   will be available after push at:"
logecho "#   ${K8S_GITHUB_URL}/compare/${rev_origin_release}...${rev_head}"
logecho "* git log origin/${RELEASE_BRANCH}..HEAD"
logecho

if common::askyorn -e "OK to push now"; then
  # Finally push it upstream
  logecho -n "Pushing$DRYRUN_FLAG $RELEASE_BRANCH branch upstream: "
  logrun -s git push $DRYRUN_FLAG origin $RELEASE_BRANCH:$RELEASE_BRANCH
fi

logecho

if (($FLAGS_cleanup)); then
  logecho "$BASEDIR has been removed, as the --cleanup flag was provided."
else
  logecho "$WORKDIR left intact for reviewing"
fi

# END script
common::timestamp end
